iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0
Vue.js

從零開始的Vue之旅系列 第 24

vue小專案-資料持久化 part4- firebase_3

  • 分享至 

  • xImage
  •  

今天加緊腳步把ProjectTracker.vue的資料也存到firebase去
先處理firestore database的部分,新增一個集合來存ProjectTracker.vue的資料
集合的名稱各位可以自行決定,如果是跟著做專案進度追蹤的可以參考以下的欄位設定
https://ithelp.ithome.com.tw/upload/images/20251006/20178690No3krky2pB.png
我是先將集合命名成projects,然後設定欄位
專案會包括的元素有專案名稱(string),其中的子任務(array),判斷專案是否完成(boolean)
而每個子任務會包含的元素有任務名稱(string),判斷任務是否完成(boolean)
將包含子任務的陣列名稱設定為subtasks,並在裡面新增物件型別的元素
一個map中可以同時包含多個欄位設定
所以我們要存子任務的話,先用map將任務名稱和是否完長的判斷狀態包在一起
在陣列中建立多個map就是建立多個子任務

完成firestore database中集合的建立後,就來處理程式碼吧
先到ProjectTracker.vue<script setup>區域,先刪除所有LocalStorage 邏輯
刪掉這些程式碼

// === 讀取 LocalStorage ===
const savedProjects = localStorage.getItem("projects")
if (savedProjects) {
  projects.value = JSON.parse(savedProjects)
}
// === 監聽 projects 變化並存入 LocalStorage ===
watch(projects, (newVal) => {
  localStorage.setItem("projects", JSON.stringify(newVal))
}, { deep: true })

然後import這些

import { ref, onMounted  } from 'vue'
import { db } from '@/firebase'
import { collection, getDocs, addDoc, updateDoc, deleteDoc, doc } from "firebase/firestore"

添加這幾段程式碼

// 讀取 Firestore 專案資料
async function loadProjects() {
  const querySnapshot = await getDocs(collection(db, "projects"))
  projects.value = querySnapshot.docs.map(doc => ({
    id: doc.id,
    ...doc.data()
  }))
}
// 一開始載入資料
onMounted(() => {
  loadProjects()
})

要注意 const querySnapshot = await getDocs(collection(db, "projects"))
這裡的"projects"是你自己設定的集合名稱

接著修改function

// 讀取 Firestore 專案資料
async function loadProjects() {
  const querySnapshot = await getDocs(collection(db, "projects"))
  projects.value = querySnapshot.docs.map(doc => ({
    id: doc.id,
    ...doc.data()
  }))
}


// 新增專案
async function addProject(title) {
  if (title.trim() !== '') {
    const newProj = {
      title,
      done: false,
      subtasks: []
    }
    const docRef = await addDoc(collection(db, "projects"), newProj)
    projects.value.push({ id: docRef.id, ...newProj })
    newProject.value = ''
  }
}


// 新增子任務
async function addSubtask(projectIndex, text) {
  if (text.trim() === '') return

  const project = projects.value[projectIndex]
  const updatedSubtasks = [...project.subtasks, { text, done: false }]
 
  const docRef = doc(db, "projects", project.id)
  await updateDoc(docRef, { subtasks: updatedSubtasks })

  project.subtasks = updatedSubtasks
  project.newSubtask = ''
}

// 切換子任務完成狀態
async function toggleSubtask(projectIndex, subtaskIndex) {
  const project = projects.value[projectIndex]
  const subtasks = [...project.subtasks]
  subtasks[subtaskIndex].done = !subtasks[subtaskIndex].done

  await updateDoc(doc(db, "projects", project.id), { subtasks })
  project.subtasks = subtasks
}

// 刪除專案
async function deleteProject(pIndex) {
  const id = projects.value[pIndex].id
  await deleteDoc(doc(db, "projects", id))
  projects.value.splice(pIndex, 1)
}

// 刪除子任務
async function deleteSubtask(pIndex, sIndex) {
  const project = projects.value[pIndex]
  const updatedSubtasks = [...project.subtasks]
  updatedSubtasks.splice(sIndex, 1)

  await updateDoc(doc(db, "projects", project.id), { subtasks: updatedSubtasks })
  project.subtasks = updatedSubtasks
}

// 計算進度
function projectProgress(project) {
  if (project.subtasks.length === 0) return 0
  const doneCount = project.subtasks.filter(s => s.done).length
  return Math.round((doneCount / project.subtasks.length) * 100)
}

我記得<template>沒修改甚麼,各位修改完<script setup>區域的程式應該能在測試頁面
看到剛才新建的專案,以防萬一我還是附上<script setup><template>區的程式碼

<script setup>
import { ref, onMounted  } from 'vue'
import { db } from '@/firebase'
import { collection, getDocs, addDoc, updateDoc, deleteDoc, doc } from "firebase/firestore"

// 儲存專案清單
const projects = ref([])

// 專案輸入框
const newProject = ref('')

// 讀取 Firestore 專案資料
async function loadProjects() {
  const querySnapshot = await getDocs(collection(db, "projects"))
  projects.value = querySnapshot.docs.map(doc => ({
    id: doc.id,
    ...doc.data()
  }))
}

// 一開始載入資料
onMounted(() => {
  loadProjects()
})

// 新增專案
async function addProject(title) {
  if (title.trim() !== '') {
    const newProj = {
      title,
      done: false,
      subtasks: []
    }
    const docRef = await addDoc(collection(db, "projects"), newProj)
    projects.value.push({ id: docRef.id, ...newProj })
    newProject.value = ''
  }
}

// 新增子任務
async function addSubtask(projectIndex, text) {
  if (text.trim() === '') return

  const project = projects.value[projectIndex]
  const updatedSubtasks = [...project.subtasks, { text, done: false }]

  const docRef = doc(db, "projects", project.id)
  await updateDoc(docRef, { subtasks: updatedSubtasks })

  project.subtasks = updatedSubtasks
  project.newSubtask = ''
}

// 切換子任務完成狀態
async function toggleSubtask(projectIndex, subtaskIndex) {
  const project = projects.value[projectIndex]
  const subtasks = [...project.subtasks]
  subtasks[subtaskIndex].done = !subtasks[subtaskIndex].done
  await updateDoc(doc(db, "projects", project.id), { subtasks })
  project.subtasks = subtasks
}

// 刪除專案
async function deleteProject(pIndex) {
  const id = projects.value[pIndex].id
  await deleteDoc(doc(db, "projects", id))
  projects.value.splice(pIndex, 1)
}

// 刪除子任務
async function deleteSubtask(pIndex, sIndex) {
  const project = projects.value[pIndex]
  const updatedSubtasks = [...project.subtasks]
  updatedSubtasks.splice(sIndex, 1)

  await updateDoc(doc(db, "projects", project.id), { subtasks: updatedSubtasks })
  project.subtasks = updatedSubtasks
}

// 計算進度
function projectProgress(project) {
  if (project.subtasks.length === 0) return 0
  const doneCount = project.subtasks.filter(s => s.done).length
  return Math.round((doneCount / project.subtasks.length) * 100)
}

</script>


<template>
  <div class="project-tracker">
    <h2>📊 專案進度追蹤</h2>

    <!-- 新增專案 -->
    <div class="input-box">
      <input
        v-model="newProject"
        placeholder="輸入專案名稱..."
        @keyup.enter="addProject(newProject); newProject=''"
      />
      <button @click="addProject(newProject); newProject=''">新增專案</button>
    </div>

    <!-- 專案清單 -->
    <div v-for="(project, pIndex) in projects" :key="pIndex" class="project-box">
      <h3>{{ project.title }}  
        <!-- 刪除專案按鈕 -->
        <button  class="deletProjectButton"  @click="deleteProject(pIndex)">❌ 刪除專案</button>
      </h3>

      <!-- 進度條 -->
      <div class="progress-bar">
        <div
          class="progress"
          :style="{ width: projectProgress(project) + '%' }"
        ></div>
      </div>
      <p>{{ projectProgress(project) }}%</p>

      <!-- 新增子任務 -->
      <div class="input-box">
        <input
          v-model="project.newSubtask"
          placeholder="輸入子任務..."
          @keyup.enter="addSubtask(pIndex, project.newSubtask); project.newSubtask=''"
        />
        <button @click="addSubtask(pIndex, project.newSubtask); project.newSubtask=''">新增子任務</button>
      </div>

      <!-- 子任務清單 -->
      <ul class="task-list">
        <li
          v-for="(subtask, sIndex) in project.subtasks"
          :key="sIndex"
          :class="{ done: subtask.done }"
        >
          <span @click="toggleSubtask(pIndex, sIndex)">  
            <input
              type="checkbox"
              v-model="subtask.done"
          />{{ subtask.text }}</span>
           <!-- 刪除子任務按鈕 -->
          <button class="deletSubtaskButton" @click="deleteSubtask(pIndex, sIndex)">🗑 刪除</button>

        </li>
      </ul>
    </div>
  </div>
 
</template>

這是我目前測試網頁的畫面和資料庫的畫面
https://ithelp.ithome.com.tw/upload/images/20251006/20178690vHTPzscLsX.png
https://ithelp.ithome.com.tw/upload/images/20251006/20178690ST3nhyA40c.png
好的終於將資料都成功轉到firebase了
祝各位中秋節快樂~
各位明天見~


上一篇
vue小專案-資料持久化 part3- firebase_2
下一篇
小閒聊_談談Firebase
系列文
從零開始的Vue之旅25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言